package com.ziiwee.reduce.spring;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.metadata.ConstraintDescriptor;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @author ziiwee
 * @since 2021/3/2
 */
public abstract class AbstractExceptionResolver implements HandlerExceptionResolver {
    protected final Log log = LogFactory.getLog(getClass());

    protected final ObjectMapper objectMapper;

    public AbstractExceptionResolver(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    protected void writeErrorMessage(HttpServletRequest request, HttpServletResponse response, HttpStatus httpStatus, String message, List<ErrorInfo> errorInfos) {
        try {
            Error error = new Error(
                    httpStatus,
                    request.getServletPath(),
                    errorInfos);
            error.setMessage(message);
            String json = objectMapper.writeValueAsString(error);
            response.setStatus(httpStatus.value());
            response.setContentType(MediaType.APPLICATION_JSON.toString());
            response.setCharacterEncoding(Charset.defaultCharset().name());
            response.getWriter().println(json);
        } catch (Exception e) {
            log.error("写入错误消息失败 : " + e.getMessage(), e);
        }
    }

    protected void writeViolationErrorMessage(HttpServletRequest request, HttpServletResponse response, String message, Collection<ConstraintViolation<?>> violations) {
        List<ErrorInfo> errorInfos = new ArrayList<>();
        for (ConstraintViolation<?> v : violations) {
            ConstraintDescriptor<?> descriptor = v.getConstraintDescriptor();
            errorInfos.add(new ErrorInfo(
                    v.getRootBeanClass().getSimpleName(),
                    v.getPropertyPath().toString(),
                    v.getInvalidValue(),
                    v.getMessage(),
                    descriptor.getAnnotation().annotationType().getSimpleName(),
                    descriptor.getAttributes()
            ));
        }
        writeErrorMessage(request, response, HttpStatus.BAD_REQUEST, message, errorInfos);
    }

    @Data
    public static class Error {
        private Date timestamp = new Date();
        private int status;
        private String error;
        private String message;
        private String path;
        private List<ErrorInfo> errorInfos;

        public Error(HttpStatus status, String path, List<ErrorInfo> errorInfos) {
            this.status = status.value();
            this.error = status.getReasonPhrase();
            this.path = path;
            this.errorInfos = errorInfos;
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class ErrorInfo {
        private String domain;
        private String field;
        private Object value;
        private String message;
        private String rule;
        private Map<String, Object> ruleParameters;
    }

}
